import { YForm, YInterception, YItemConfig, YItemRow } from '../interface';
import ItemConfig from './ItemConfig';
import ItemRow from './ItemRow';
import ItemText from './ItemText';

type NodeType = ItemRow | ItemConfig | ItemRowList | ItemText | null;

export default class ItemRowList {
  next: NodeType;
  prev: NodeType;
  head: ItemConfig;
  constructor(
    next: NodeType = null,
    prev: NodeType = null,
    public form: YForm
  ) {
    this.next = next;
    this.prev = prev;
    this.head = new ItemConfig({ type: '' }, null, null, this.form);
  }

  findLast() {
    let currNode: NodeType = this.head;
    while (currNode && currNode.next) {
      currNode = currNode.next;
    }
    return currNode;
  }

  toItemConfig(node: ItemRow | ItemConfig | ItemRowList | ItemText) {
    const config = (node as ItemConfig).data;

    if (typeof config !== 'object') {
      return null;
    }
    if (config.type) {
      return node as ItemConfig;
    }
    return null;
  }

  toItemRow(node: ItemRow | ItemConfig | ItemRowList | ItemText) {
    const config = (node as ItemRow).data;
    if (typeof config !== 'object') {
      return null;
    }
    if (config.children) {
      return node as ItemRow;
    }
    return null;
  }

  toItemRowList(node: ItemRow | ItemConfig | ItemRowList | ItemText) {
    const head = (node as ItemRowList).head;
    if (head) {
      return node as ItemRowList;
    }
    return null;
  }

  add(
    currNode:
      | YItemRow
      | (YItemRow | YItemConfig)[]
      | YItemConfig
      | string
      | number
      | boolean
  ) {
    const lastNode = this.findLast();
    if (currNode === undefined || currNode === null) {
      return;
    }
    if (
      typeof currNode === 'string' ||
      typeof currNode === 'number' ||
      typeof currNode === 'boolean'
    ) {
      lastNode.next = new ItemText(currNode, null, lastNode, this.form);
      return;
    }

    if ('type' in currNode) {
      lastNode.next = new ItemConfig(
        currNode as YItemConfig,
        null,
        lastNode,
        this.form
      );
    } else if ('children' in currNode) {
      lastNode.next = new ItemRow(
        currNode as YItemRow,
        null,
        lastNode,
        this.form
      );
    } else if (Array.isArray(currNode)) {
      const itemRowList = new ItemRowList(null, lastNode, this.form);
      currNode.forEach((item) => itemRowList.add(item));
      lastNode.next = itemRowList;
    }
  }

  getConfig(interception?: YInterception) {
    let currNode = this.head.next;
    const configs: any[] = [];
    while (currNode) {
      if ('getConfig' in currNode) {
        const config = currNode.getConfig(interception);
        configs.push(config);
      }

      if (currNode.next) {
        currNode = currNode.next;
      } else {
        break;
      }
    }
    return configs;
  }

  setDisabled(disable: boolean, props?: string[]) {
    let currNode = this.head.next;
    while (currNode) {
      if ('setDisabled' in currNode) {
        currNode.setDisabled(disable, props);
      }
      if (currNode.next) {
        currNode = currNode.next;
      } else {
        return;
      }
    }
  }

  setBind(props: string | string[], key: string, value: any) {
    let currNode = this.head.next;
    while (currNode) {
      if ('setBind' in currNode) {
        currNode.setBind(props, key, value);
      }
      if (currNode.next) {
        currNode = currNode.next;
      } else {
        return;
      }
    }
  }

  setRequired(required: boolean, props?: string[]) {
    let currNode = this.head.next;
    while (currNode) {
      if ('setRequired' in currNode) {
        currNode.setRequired(required, props);
      }
      if (currNode.next) {
        currNode = currNode.next;
      } else {
        return;
      }
    }
  }

  setOptions(prop: string, options: { [key: string]: any }[]) {
    let currNode = this.head.next;
    while (currNode) {
      if ('setRequired' in currNode) {
        currNode.setOptions(prop, options);
      }
      if (currNode.next) {
        currNode = currNode.next;
      } else {
        return;
      }
    }
  }

  setOptionsMap(options: { [key: string]: { [key: string]: any }[] }) {
    Object.keys(options).forEach((key) => {
      this.setOptions(key, options[key]);
    });
    return this;
  }

  setConfig(prop: string, config: YItemConfig) {
    let currNode = this.head.next;
    while (currNode && currNode.next) {
      const itemConfig = this.toItemConfig(currNode.next);
      if (itemConfig && itemConfig.data.prop) {
        if (prop === itemConfig.data.prop) {
          itemConfig.setConfig({ ...config, prop });
        }
      }
      const itemRow = this.toItemRow(currNode.next);
      if (itemRow) {
        itemRow.setConfig(prop, config);
      }
      const itemRowList = this.toItemRowList(currNode.next);
      if (itemRowList) {
        itemRowList.setConfig(prop, config);
      }
      currNode = currNode.next;
    }
  }

  removeConfig(props: string | string[]) {
    let currNode: NodeType = this.head;
    if (typeof props === 'string') {
      props = [props];
    }
    while (currNode && currNode.next) {
      const itemConfig = this.toItemConfig(currNode.next);
      if (itemConfig && itemConfig.data.prop) {
        const prop = this.form.createEntity.getConfigFile(
          itemConfig.data,
          'prop'
        );
        if (props.includes(prop)) {
          if (itemConfig.next) {
            itemConfig.next.prev = currNode;
          }
          currNode.next = itemConfig.next;
        } else {
          currNode = currNode.next;
        }
        continue;
      }
      const itemRow = this.toItemRow(currNode.next);
      if (itemRow) {
        itemRow.removeConfig(props);
      }

      const itemRowList = this.toItemRowList(currNode.next);
      if (itemRowList) {
        itemRowList.removeConfig(props);
      }
      currNode = currNode.next;
    }
  }

  appendConfigBefore(prop: string, config: YItemConfig) {
    let currNode: NodeType = this.head;
    while (currNode && currNode.next) {
      const itemConfig = this.toItemConfig(currNode.next);
      if (itemConfig) {
        const node = itemConfig.appendConfigBefore(prop, config);
        if (node) {
          currNode = node.next;
          continue;
        }
      }
      const itemRow = this.toItemRow(currNode.next);
      if (itemRow) {
        itemRow.appendConfigBefore(prop, config);
      }

      const itemRowList = this.toItemRowList(currNode.next);
      if (itemRowList) {
        itemRowList.appendConfigBefore(prop, config);
      }
      currNode = currNode.next;
    }
  }
  appendConfigAfter(prop: string, config: YItemConfig) {
    let currNode: NodeType = this.head.next;
    while (currNode) {
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemConfig.appendConfigAfter(prop, config);
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemRow.appendConfigAfter(prop, config);
      }

      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRowList.appendConfigAfter(prop, config);
      }

      currNode = currNode.next;
    }
  }

  replaceConfig(prop: string, config: YItemConfig) {
    let currNode: NodeType = this.head.next;
    while (currNode) {
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig && prop === itemConfig.data.prop) {
        currNode = new ItemConfig(config, currNode.next, null, this.form);
        continue;
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemRow.replaceConfig(prop, config);
      }

      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRowList.replaceConfig(prop, config);
      }

      currNode = currNode.next;
    }
  }

  removeRow(id: string | number) {
    let currNode: NodeType = this.head.next;
    while (currNode) {
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemConfig.removeRow(id);
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemRow.removeRow(id);
      }

      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRowList.removeRow(id);
      }

      currNode = currNode.next;
    }
  }

  appendRowBefore(
    id: string | number,
    config: YItemConfig | YItemRow | YItemRow[]
  ) {
    let currNode: NodeType = this.head.next;
    while (currNode) {
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemConfig.appendRowBefore(id, config);
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        const node = itemRow.appendRowBefore(id, config);
        if (node) {
          currNode = node.next;
          continue;
        }
      }
      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRowList.appendRowBefore(id, config);
      }

      currNode = currNode.next;
    }
  }
  appendRowAfter(
    id: string | number,
    config: YItemConfig | YItemRow | YItemRow[]
  ) {
    let currNode: NodeType = this.head.next;
    while (currNode) {
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemConfig.appendRowAfter(id, config);
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemRow.appendRowAfter(id, config);
      }

      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRowList.appendRowAfter(id, config);
      }

      currNode = currNode.next;
    }
  }

  getItemConfigs() {
    let currNode: NodeType = this.head.next;
    let configs: YItemConfig[] = [];
    while (currNode) {
      let itemConfigs: YItemConfig[] = [];
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemConfigs = itemConfig.getItemConfigs();
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemConfigs = itemRow.getItemConfigs();
      }
      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemConfigs = itemRowList.getItemConfigs();
      }
      configs = configs.concat(itemConfigs);
      currNode = currNode.next;
    }
    return configs;
  }

  getRows() {
    let currNode: NodeType = this.head.next;
    let configs: YItemRow[] = [];
    while (currNode) {
      let itemRows: YItemRow[] = [];
      const itemConfig = this.toItemConfig(currNode);
      if (itemConfig) {
        itemRows = itemConfig.getRows();
      }
      const itemRow = this.toItemRow(currNode);
      if (itemRow) {
        itemRows = itemRow.getRows();
      }
      const itemRowList = this.toItemRowList(currNode);
      if (itemRowList) {
        itemRows = itemRowList.getRows();
      }
      configs = configs.concat(itemRows);
      currNode = currNode.next;
    }
    return configs;
  }
}
